/*************************************************************************************
 * The MOSES project, model based software engineering of networked embedded systems.
 * 
 * Copyright (C) 2010-2013, University of Applied Sciences Wildau,
 * Telematics Research Group, http://www.tm.th-wildau.de/research-group
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 * 
 * Contributors:
 *     University of Applied Sciences Wildau - initial API and implementation
 * 
 * If you are interested in a commercial license please contact us via 
 * e-mail: ralf.vandenhouten@th-wildau.de.
 *  
 *************************************************************************************/
package de.thwildau.tm.moses.abnf.generator

import de.thwildau.tm.moses.abnf.abnf.Alternation
import de.thwildau.tm.moses.abnf.abnf.CharVal
import de.thwildau.tm.moses.abnf.abnf.Group
import de.thwildau.tm.moses.abnf.abnf.Repeat
import de.thwildau.tm.moses.abnf.abnf.Rule
import de.thwildau.tm.moses.abnf.abnf.RuleRef
import de.thwildau.tm.moses.abnf.abnf.Option
import org.eclipse.xtext.util.Tuples
import org.eclipse.xtext.util.Pair
import com.google.inject.Inject
import java.util.List
import org.apache.log4j.Logger
import de.thwildau.tm.moses.abnf.abnf.RuleSet
import de.thwildau.tm.moses.abnf.abnf.Concatenation
import de.thwildau.tm.moses.abnf.abnf.Repetition
import de.thwildau.tm.moses.xabnf.xabnf.RuleBinding
import de.thwildau.tm.moses.abnf.abnf.ProseVal
import de.thwildau.tm.moses.abnf.abnf.BinVal
import de.thwildau.tm.moses.abnf.abnf.DecVal
import de.thwildau.tm.moses.abnf.abnf.HexVal
import de.thwildau.tm.moses.abnf.generator.model.AbnfRuleClass
import de.thwildau.tm.moses.abnf.generator.model.AbnfRepeat
import de.thwildau.tm.moses.abnf.generator.model.AbnfElementField
import de.thwildau.tm.moses.abnf.generator.model.AbnfCharValue
import de.thwildau.tm.moses.abnf.generator.model.impl.ModelFactory
import de.thwildau.tm.moses.abnf.generator.model.impl.AbnfRuleClassImpl
import de.thwildau.tm.moses.abnf.generator.model.impl.AbnfAlternationImpl
import de.thwildau.tm.moses.abnf.generator.model.impl.AbnfAttributeImpl
import de.thwildau.tm.moses.abnf.generator.model.impl.AbnfElementFieldImpl

/*
 * This model inferrer infers the ABNF grammar (Xtext) model into an intermediate
 * model that is used for the real code generation by the AbnfGenerator.
 */
class ModelInferrer {

	static val LOG = Logger::getInstance(typeof(ModelInferrer))

	@Inject
	private NameResolver nameResolver;
	
	@Inject
	private extension ModelUtil modelUtil

	def List<AbnfRuleClass> createModel(Iterable<Rule> rules) {
		val List<AbnfRuleClass> ruleClasses = newArrayList()

		// create the direct model classes for each rule
		for (Rule r : rules) {
			val ruleModel = r.createModel
			val ele = ruleClasses.find(ruleModel.className)
			if (ele == null) {
				ruleClasses.add(ruleModel)
			} else {
				LOG.warn(
					"A model class of " + ruleModel.className + " already exist with a different case " + ele.className)
			}
		}

		// create the super classes by inferring the rule references
		for (AbnfRuleClass rc : ruleClasses) {
			val abnfRuleClass = rc as AbnfRuleClassImpl
			val superClass = ruleClasses.getSuperClass(abnfRuleClass.rule)
			abnfRuleClass.setSuperClass(superClass)
		}

		ruleClasses
	}

	private def createModel(Rule rule) {
		val className = nameResolver.getFqn(rule).lastSegment
		val AbnfRuleClassImpl model = ModelFactory::createAbnfRuleClass(rule, className) as AbnfRuleClassImpl
		model.createModelFromAlternation(rule.alternation, null)
		model.normalizeAttributes
		model.bindAttributeNames
		model
	}

	/*
	 * Returns the class name of the cross reference of the given rule. The returned class name
	 * does not contain the package name.
	 */
	private def AbnfRuleClass getSuperClass(List<AbnfRuleClass> ruleClasses, Rule rule) {
		val ruleSet = rule.eContainer as RuleSet
		for (Rule r : ruleSet.rules) {
			if (!r.equals(rule)) {
				for (Concatenation c : r.alternation.concatenations) {
					for (Repetition rp : c.repetitions) {
						var element = rp.element

						//						if(element instanceof RuleRef) {
						//							var ruleRef = element as RuleRef
						//							if(ruleRef.rule.name.equals(rule.name)) {
						//								println("(RulRef)return: " + r.name)
						//								return r.name.toLowerCase.toFirstUpper +"s"
						//							}
						//						} 
						if (element instanceof Group) {
							val group = element as Group
							for (Concatenation groupConcatenation : group.alternation.concatenations) {
								for (Repetition groupRp : groupConcatenation.repetitions) {
									if (groupRp.element instanceof RuleRef) {
										val groupRuleRef = groupRp.element as RuleRef
										if (groupRuleRef.rule.equals(rule) &&
											group.alternation.concatenations.size > 1) {
											for (rc : ruleClasses) {
												if ((rc as AbnfRuleClassImpl).rule.equals(r)) {
													LOG.debug("Set super class of " + rule.name + " to " + r.name)
													return rc
												}	
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return null
	}

	private def AbnfRuleClass find(List<AbnfRuleClass> ruleClasses, String className) {
		ruleClasses.findFirst[modelClass|modelClass.className.equalsIgnoreCase(className)]
	}

	/**
	 * This function normalizes the AbnfAttributes in the AbnfModel. It look for Attributes
	 * with the same name, counts them. Those attributes with count > 1 will get a new
	 * indexed name (e.g. attName1, attName2...).
	 * 
	 * @param model - the Abnf model whose attributes should be normalized
	 */
	def normalizeAttributes(AbnfRuleClassImpl model) {
		val map = <String, Pair<Integer, Integer>>newHashMap();
		var attribtues = model.getElements.filter(typeof(AbnfAttributeImpl))

		// count the attributes with the same name
		attribtues.toMap(
			[
				var value = map.get(name) as Pair<Integer, Integer>;
				val integer = value?.first
				val index = if(integer == null) 0 else integer.intValue
				map.put(name, Tuples::create(index + 1, 0))
				return map
			])

		// rename those attributes with count > 1
		attribtues.forEach(
			[ ele, i |
				var name = ele.name
				var counterPair = map.get(name)
				if (counterPair?.first > 1) {
					ele.setName(name + (counterPair.second + 1))
					map.put(name, Tuples::create(counterPair.first, counterPair.second + 1))
				}
			])
	}

	/**
	 * Binds the attributes names of the Xabnf-Binding to the attributes names of the AbnfRuleClass.
	 * If no Xabnf-Binding is found for this AbnfRuleClass, this method does nothing.
	 * 
	 * @param model - the Abnf model whose attrbutes should be bound to specific names as specified in an Xabnf file
	 */
	def bindAttributeNames(AbnfRuleClassImpl rc) {
		val ruleBinding = nameResolver.getXabnfContent(rc.rule.eResource).filter(typeof(RuleBinding)).findFirst[rb | rb.rule.equals(rc.rule)]
		if(ruleBinding != null && ruleBinding.fieldBindings.size > 0) {
			var index = 0;
			for(field : rc.elements.filter(typeof(AbnfAttributeImpl))) {
				if(index < ruleBinding.fieldBindings.size) {
					val attBinding = ruleBinding.fieldBindings.get(index)
					LOG.debug("Bind " + rc.className + " [" + field.type + "] " + field.name + " => " + attBinding.targetName)
					field.setName(attBinding.targetName)
					index = index + 1	
				}
			}	
		}
	}

	def AbnfRuleClassImpl createModelFromAlternation(AbnfRuleClassImpl model, Alternation alternation, AbnfRepeat outerRepeat) {
		var concatenations = alternation.concatenations
		if (concatenations.size == 1) {
			for (r : concatenations.get(0).repetitions) {
				var modelRepeat = createFromBaseModel(outerRepeat, r.repeat)
				r.element.createModel(model, modelRepeat)
			}
		} else if (concatenations.size > 1) {
			var modelRepeat = createFromBaseModel(outerRepeat, null)
			var rule = ModelUtil::findRule(alternation)
			var fieldName = rule.getFieldName(modelRepeat, model)
			val alternationModel = ModelFactory::createAbnfAlternation(modelRepeat, fieldName.toFirstUpper, fieldName) as AbnfAlternationImpl<AbnfElementField>
			for (c : concatenations) {

				//				val charValues = <CharVal>newArrayList()
				c.repetitions.forEach(
					[
						if (element instanceof CharVal) {

							//						charValues.add(element as CharVal)
							alternationModel.add(
								ModelFactory::createAbnfCharValue(createFromBaseModel(repeat), (element as CharVal).value))
						} else if (element instanceof RuleRef) {
							val ruleRef = element as RuleRef
							alternationModel.add(
								ModelFactory::createAbnfAttribute(createFromBaseModel(repeat), getCoreRuleRefOrRuleName(ruleRef),
									ruleRef.rule.name))
						}
					])

				//				for(charValue : charValues) {
				//					alternationModel.add(charValue.value)
				//				}
				}
				model.add(alternationModel)
			}
			model
		}

		private def String getCoreRuleRefOrRuleName(RuleRef ruleRef) {
			val ruleName = ruleRef.rule.name
			val coreRuleRef = ModelUtil::getCoreRuleReference(ruleName)
			return if(coreRuleRef != null) coreRuleRef else ruleName
		}

		def dispatch createModel(Group element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			model.createModelFromAlternation(element.alternation, modelRepeat)
		}

		def dispatch createModel(Option element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {

			// TODO handle Option element
			//		println("got option of " + element.alternation)
			model.createModelFromAlternation(element.alternation, createOptionalRepeat)
		}

		def dispatch createModel(RuleRef element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			var rule = element.rule;
			var fieldName = rule.getFieldName(modelRepeat, model)
			if (ModelUtil::isSimpleCoreRule(rule)) {
				model.add(ModelFactory::createAbnfCoreRule(modelRepeat, rule.name))
			} else {
				val String ruleRef = ModelUtil::getCoreRuleReference(rule.name)
				if (ruleRef != null) {
					// override the repeat attribute, because the predefined Core-Rules does not support Repeats yet
					model.add(ModelFactory::createAbnfAttribute(createDefaultRepeat(), ruleRef, fieldName))
				} else {
					model.add(ModelFactory::createAbnfAttribute(modelRepeat, rule.name, fieldName))
				}
			}
		}

		def dispatch createModel(CharVal element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			model.add(ModelFactory::createAbnfCharValue(modelRepeat, element.value))
		}
		
		def dispatch createModel(ProseVal element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			
		}
		
		def dispatch createModel(BinVal element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			
		}
		
		def dispatch createModel(DecVal element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			
		}
		
		def dispatch createModel(HexVal element, AbnfRuleClassImpl model, AbnfRepeat modelRepeat) {
			
		}

		def getFieldName(Rule rule, AbnfRepeat abnfRepeat, AbnfRuleClassImpl ruleElements) {
			var list = ruleElements.getElements
			var fieldName = "";
			if (list.size > 0) {
				var abnfElement = list.get(list.size() - 1)
				var charValue = (abnfElement as AbnfElementFieldImpl).toSubtype(typeof(AbnfCharValue))
				fieldName = JavaTemplates::toFieldName(charValue)
			}

			if (fieldName == null || fieldName.empty) {
				fieldName = rule.toFieldName

				// if we have constructed the fieldName from the name of the rule, we add a 's'
				// when we have a loop to indicate that this is a list field
				if (abnfRepeat.loop) {
					fieldName = fieldName.concat("s")
				}
			}

			//		val fieldNameParam = fieldName
			//		var nameList = ruleElements.elements.filter(typeof(AbnfAttribute)).filter([getName.equals(fieldNameParam)])
			//		if(nameList.size > 0) {
			//			var suffix = String::valueOf(nameList.size + 1)
			//			fieldName = fieldName.concat(suffix)
			//		}
			return fieldName
		}

		def toFieldName(Rule rule) {
			var name = rule.name
			return name.toLowerCase
		}

	/*
	 * Creates a default AbnfRepeat object with upper/lower bound = 1
	 */
	private def AbnfRepeat createDefaultRepeat() {
		return ModelFactory::createAbnfRepeat(1, 1)
	}

		/*
     * Creates an AbnfRepeat which indicates an optional element. The
     * AbnfRepeat has lowerBound=0 and upperBound=1.
     */
		private def AbnfRepeat createOptionalRepeat() {
			return ModelFactory::createAbnfRepeat(0, 1)
		}

		def createFromBaseModel(AbnfRepeat outerRepeat, Repeat innerRepeat) {
			var AbnfRepeat inner = innerRepeat.createFromBaseModel()
			if (outerRepeat != null) {

				// multiply the bounds (use long to prevent overflow of Integer range)
				var lower = outerRepeat.lowerBound as long * inner.lowerBound as long
				var upper = outerRepeat.upperBound as long * inner.upperBound as long

				// cut the values to the Integer range
				lower = Math::min(Integer::MAX_VALUE, lower)
				upper = Math::min(Integer::MAX_VALUE, upper)
				return ModelFactory::createAbnfRepeat(lower as int, upper as int)
			} else {
				return inner
			}
		}

		def createFromBaseModel(Repeat repeat) {
			if (repeat == null)
				return createDefaultRepeat()
			if (repeat.upperBound < 1)
				repeat.upperBound = Long::MAX_VALUE
			return ModelFactory::createAbnfRepeat(repeat.getLowerBound, repeat.getUpperBound)
		}
	}
	